import { defineStore } from 'pinia'
import { GeometryType, ShapesType, Pointer } from '@/graph/3d/config/shapes'
import { HistorySnapshot } from '@/utils/historySnapshot'
import { pointer2shape } from '@/views/3d/hooks/useFormulaElement'

export interface Variable {
  name: string
  value: number
  min: number
  max: number
  step: number
  playing: boolean
  direction: -1 | 1
  speed: number
}

export interface FormulaElement {
  id: string
  type: GeometryType
  name: string
  content: string
  visible: boolean
  style: any
  refs: string[]
  points: string[]
  shapes: ShapesType[]
  variable: Variable // 变量
  result: any // 运算结果
  dependencies: Set<string>
  error: string
  isShape: boolean
  formulaStr: string
}

export interface FormulaEditorState {
  elements: FormulaElement[]
  dependencyGraph: Map<string, string[]>
  playingElementsMap: Set<string>
}

export const useFormulaEditor = defineStore('formulaEditor', {
  state: (): FormulaEditorState => ({
    elements: [], // 元素列表
    dependencyGraph: new Map(), // 依赖图
    playingElementsMap: new Set(), // 正在运动的元素ID集合
  }),
  getters: {
    shapes(state) {
      return state.elements.map((item) => item.shapes).flat()
    },
    pointer(state) {
      return state.elements.filter((v) => v.type === GeometryType.Pointer)
    },
    hoveredPointer(state) {
      const list = state.elements
        .filter((v) => v.type === GeometryType.Pointer)
        .map((item) => item.shapes)
        .flat() as Pointer[]

      return list.find((item) => item.hovered)
    },
    playingElements(state) {
      return state.elements.filter((v) => state.playingElementsMap.has(v.id))
    },
  },
  actions: {
    setElements(elements: FormulaElement[]) {
      this.elements = elements
    },
    addElement(element: FormulaElement | FormulaElement[]) {
      const elements = Array.isArray(element) ? element : [element]
      this.elements.push(...elements)

      HistorySnapshot.add()
    },
    removeElement(id: string | string[]) {
      const ids = Array.isArray(id) ? id : [id]
      this.elements = this.elements.filter((item) => !ids.includes(item.id))

      ids.forEach((id) => {
        if (this.playingElementsMap.has(id)) {
          this.playingElementsMap.delete(id)
        }
      })

      HistorySnapshot.add()
    },
    updateElement(data: Partial<FormulaElement>[]) {
      const elIdList = data.map((item) => item.id)
      this.elements = this.elements.map((item) => {
        if (elIdList.includes(item.id)) {
          const props = data.find((v) => v.id === item.id)

          return { ...item, ...props }
        }
        return item
      })

      HistorySnapshot.add()
    },
    updatePointer(data: Partial<Pointer>[]) {
      const elIdList = data.map((item) => item.formulaId)

      let needUpdate = false
      const list = this.pointer
      const elements: FormulaElement[] = []
      for (let i = 0; i < list.length; i++) {
        const item = list[i]
        if (elIdList.includes(item.id)) {
          const props = data.find((v) => v.formulaId === item.id)
          if (props) {
            const shapes = item.shapes.map((v) => {
              if (v.id === props.id) {
                return { ...v, ...props }
              }
              return v
            })
            const element = { ...item, shapes }
            if ('position' in props) {
              needUpdate = true
              const content = `${element.name}=(${props.position?.join(',') || ''})`
              element.content = content
            }
            elements.push(element)
          }
        }
      }

      if (needUpdate) {
        elements.forEach((item) => pointer2shape(item))
      }
      else {
        this.updateElement(elements)
      }
    },
    updateDependencyGraph(formulaId: string, dependencies: Set<string>) {
      this.dependencyGraph.forEach((deps, key) => {
        this.dependencyGraph.set(
          key,
          deps.filter((id) => id !== formulaId),
        )
      })

      dependencies.forEach((depId) => {
        if (!this.dependencyGraph.has(depId)) {
          this.dependencyGraph.set(depId, [])
        }
        // @ts-ignore
        this.dependencyGraph.get(depId).push(formulaId)
      })
    },
    updatePlayingElement(set: Set<string>) {
      this.playingElementsMap = set
    },
  },
})
